
import rcontrolutil
import Tkinter



strictMode=True
#strict mode- can't add nonexisting members/properties to the class
#classes deriving from RControl have attribute access overridden. 
#this means normal additional slots *cannot* be added, like mybtn.foo = 4. only mybtn._foo=4 will work.

#I could have also used Python's built in properties, but I want to make "obj.nonExistingProperty = 3" create a runtime error. This catches typos like btn.texxt='hello'
#For the bind() method, use Tkinter's syntax.

#todo: revise documentation of properties

class RControl(object): #note: adding object here changed things.
	_basecontrol = None
	_pack_orientation = rcontrolutil.V #if created by RWindow will be set by RWindow
	_layoutinfo = None #people can say w.add_button('hello',onhello,padx=3) instead of RButton('hello',onhello).add(padx=3)
	_properties = None
	_addmethod = None
	
	def _newWidgetManual(self, parent, tkclass, tklistargs=None):
		import rwindow #circ references.
		import rframe
		
		if parent==None: raise rcontrolutil.RException('Must specify a parent when creating control manually.')
		if isinstance(parent, rwindow.RWindow): tkparent =  parent._topframe._basecontrol
		elif isinstance(parent, RControl): tkparent = parent._basecontrol
		else:	
			try:
				parent.tk #were we given a tkinter widget?
			except AttributeError:
				raise rcontrolutil.RException('Not a valid parent for this widget.')
			tkparent = parent
		if tklistargs:
			self._basecontrol = tkclass(tkparent, *tklistargs)
		else:
			self._basecontrol = tkclass(tkparent)
		
		#determine pack orientation
		if isinstance(parent, rwindow.RWindow): self._pack_orientation = parent._topframe._pack_children_orientation
		elif isinstance(parent, rframe.RFrame): self._pack_orientation = parent._pack_children_orientation
		#otherwise, we don't know pack orientation, but that's ok
		
	
	#override attribute access. Note that __getattr__ and __setattr__ are not symmetrical.
	def __getattr__(self, strAttribute):
		'''Called only if the attribute does not exist.'''
		return self._getorset(bSet=False, strAttribute=strAttribute)
		
	def __setattr__(self, strAttribute, value):
		'''Called on _every_ attribute set'''
		if strAttribute.startswith('_') or not strictMode:
			#use default (normal) attribute setting.
			self.__dict__[strAttribute] = value 
			
		else:
			return self._getorset(bSet=True, strAttribute=strAttribute, value=value)
	
	
	def _getorset(self, bSet, strAttribute, value=None):
		'''Called when getting/setting a property'''
		
		if self._properties==None or strAttribute not in self._properties:
			if bSet:
				raise rcontrolutil.PyRindPropertyException("You are trying to set the '%s' of a control; this property doesn't exist."% strAttribute)
			else:
				raise rcontrolutil.PyRindPropertyException("You are trying to get the '%s' of a control; this property doesn't exist."% strAttribute)
		
		
		propertydata = self._properties[strAttribute]
		if isinstance(propertydata, basestring):
			# 1) the property is the same as the Tkinter property, just with a different name.
			if bSet: 
				self._basecontrol[propertydata] = value
			else: 
				return self._basecontrol[propertydata]
				
		elif isinstance(propertydata, tuple):
			if isinstance(propertydata[0], basestring) and isinstance(propertydata[1], rcontrolutil.PropertyMapping):
				# 2) the property uses a PropertyMapping.

				if bSet: 
					self._basecontrol[propertydata[0]] = propertydata[1].lookupForward(value)
				else: 
					return propertydata[1].lookupReverse( self._basecontrol[propertydata[0]] )
			
			else:
				# 3) the property uses custom functions
				
				fnGetWrapper, fnSetWrapper = propertydata
				if bSet:
					fnSetWrapper(self, value) #bind method to self
				else:
					return fnGetWrapper(self )
		else:
			raise rcontrolutil.RException('Internal error. bad property format.')
			
	
	#arguments that are for layout. can't cover any widget arguments
	_layoutArguments=dict(pad=None, padx=None, pady=None, fill=None, resizable=None, side=None)
	def _addPack(self, 	pad=None, padx=None, pady=None, fill=None, resizable=None, side=None):
		'''Add the control to the form using Pack method.'''
		kwds={}
		
		if padx!=None:
			kwds['padx']=padx
		if pady!=None:
			kwds['pady']=pady
		if pad!=None:
			kwds['padx']=pad
			kwds['pady']=pad
		
		if fill!=None:
			if fill==rcontrolutil.H or fill=='h' or fill=='x':
				kwds['fill'] = Tkinter.X
			elif fill==rcontrolutil.V or fill=='v' or fill=='y':
				kwds['fill'] = Tkinter.Y
			elif fill==True or fill=='both':
				kwds['fill'] = Tkinter.BOTH
			else:
				raise AttributeError, '''"fill" must be one of: 'h', 'v', True'''
		
		if resizable:
			kwds['expand'] = True
		if side!=None:
			kwds['anchor'] = side #default to Tkinter.CENTER. Not the same as the tk property 'side"
		
		#stacking from left to right or top to bottom. usually chosen by parent.
		if self._pack_orientation== rcontrolutil.H: kwds['side'] = Tkinter.LEFT
		else: kwds['side']=Tkinter.TOP
				
		self._basecontrol.pack(**kwds)
		return self
	
	#todo: document resizable, how it can be "left", 'right', etc. to mean x, and so on.
	_layoutArguments.update(dict(grid=None, colspan=None, rowspan=None, pad=None, padx=None, pady=None, resizable=None))
	def _addGrid(self,		 grid, 	colspan=None, rowspan=None, pad=None, padx=None, pady=None, resizable=None):
		kwds={}
		
		kwds['row'], kwds['column'] = grid
		if colspan!=None: kwds['columnspan']=colspan
		if rowspan!=None: kwds['rowspan']=colspan
			
		if padx!=None:
			kwds['padx']=padx
		if pady!=None:
			kwds['pady']=pady
		if pad!=None:
			kwds['padx']=pad
			kwds['pady']=pad
			
		if resizable==True:
			kwds['sticky'] = Tkinter.W+Tkinter.E+Tkinter.N+Tkinter.S
		elif resizable==False or resizable==None:
			pass
		elif isinstance(resizable, basestring) and resizable=='center':
			pass
		elif isinstance(resizable, basestring):
			resizable = resizable.lower()
			s = ''
			if 'left' in resizable: s+='w'; resizable=resizable.replace('left','',1) #so you can't say left,left.
			if 'right' in resizable: s+='e'; resizable=resizable.replace('right','',1)
			if 'top' in resizable: s+='n'; resizable=resizable.replace('top','',1)
			if 'bottom' in resizable: s+='s'; resizable=resizable.replace('bottom','',1)
			for char in resizable:
				if char.isalpha():
					raise rcontrolutil.PyRindPropertyException("For grid layout, resizable must be one or more of 'left','right','top','bottom', or alternatively just 'center'")
			
			kwds['sticky'] = s
		
		self._basecontrol.grid(**kwds)
	
	_layoutArguments.update(dict(position=None, pixel_width=None, pixel_height=None))
	def _addPlace(self, position, 			pixel_width=None, pixel_height=None):
		kwds={}
		kwds['x'], kwds['y']=position
		if pixel_width!=None:
			kwds['width'] = pixel_width
		if pixel_height!=None:
			kwds['height'] = pixel_height
		
		self._basecontrol.place(**kwds)
	
	
	
	def add(self, **args): #called manually when people add widgets manually.
		s= self._determineLayoutMethod(args)
		if s=='grid':
			self._addGrid( ** args)
			self._addmethod = 'grid'
		elif s=='place': 
			self._addPlace( ** args)
			self._addmethod = 'place'
		else: 
			self._addPack( ** args)
			self._addmethod = 'pack'
		return self
		
	def _determineLayoutMethod(self,dictArgs):
		if 'grid' in dictArgs: return 'grid'
		elif 'position' in dictArgs: return 'place'
		else:  return 'pack'
		
	def set_many(self,**options):
		"""Set many properties of the control at once."""
		# slightly inefficient to do this instead of config, which could set many properties at once, but I'm not worried about this for now
		for option in options:
			self._getorset(True, option, options[option])
	def get_prop(self,propname):
		"""Get property value."""
		return self._getorset(False, propname)
	
	#Tkinter methods
	
	def focus(self):
		self._basecontrol.focus_set()
	def has_focus(self):
		return self._basecontrol.focus_get()
	
	def delete(self): #Remove control
		self._basecontrol.destroy()

	def unadd(self): #hide control
		if self._addmethod==None:
			raise rcontrolutil.RException('Cannot unadd() before it has been added.')
		if self._addmethod=='pack': self._basecontrol.pack_forget()
		elif self._addmethod=='grid': self._basecontrol.grid_forget()
		elif self._addmethod=='place': self._basecontrol.place_forget()
		
	
	def update(self):
		self._basecontrol.update_idletasks()
		

if __name__=='__main__':
	print RControl._layoutArguments
	
